﻿using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Reflection;

namespace Sys.Excel
{
    internal static class Dynamic
    {
        private static IDictionary<FieldInfo, Func<object, object>> _fieldGetters;
        private static IDictionary<PropertyInfo, Func<object, object>> _propertyGetters;

        static Dynamic()
        {
            Dynamic._fieldGetters = new Dictionary<FieldInfo, Func<object, object>>();
            Dynamic._propertyGetters = new Dictionary<PropertyInfo, Func<object, object>>();
        }

        public static Func<object, object> PropertyGetter(PropertyInfo property)
        {
            if (property == null)
            {
                throw new ArgumentNullException(nameof(property));
            }

            if (Dynamic._propertyGetters.TryGetValue(property, out Func<object, object> getter))
            {
                return getter;
            }

            lock (Dynamic._propertyGetters)
            {
                if (Dynamic._propertyGetters.TryGetValue(property, out getter))
                {
                    return getter;
                }

                var parm = Expression.Parameter(typeof(object));

                var exp_property = Expression.Property(Expression.Convert(parm, property.DeclaringType), property);

                var lambda = Expression.Lambda<Func<object, object>>(Expression.Convert(exp_property, typeof(object)), parm);

                Dynamic._propertyGetters[property] = getter = lambda.Compile();
            }

            return getter;
        }

        public static Func<object, object> FieldGetter(FieldInfo field)
        {
            if (field == null)
            {
                throw new ArgumentNullException(nameof(field));
            }

            if (Dynamic._fieldGetters.TryGetValue(field, out var getter))
            {
                return getter;
            }

            lock (Dynamic._propertyGetters)
            {
                if (Dynamic._fieldGetters.TryGetValue(field, out getter))
                {
                    return getter;
                }

                var parm = Expression.Parameter(typeof(object));

                var exp_field = Expression.Field(Expression.Convert(parm, field.DeclaringType), field);

                var lambda = Expression.Lambda<Func<object, object>>(Expression.Convert(exp_field, typeof(object)), parm);

                Dynamic._fieldGetters[field] = getter = lambda.Compile();
            }

            return getter;
        }
    }
}